package com.gitee.wsl.markdown

import androidx.compose.runtime.Composable
import com.gitee.wsl.compose.ui.richtext.RichTextScope
import com.gitee.wsl.prop.AttributesContainer
import org.intellij.markdown.ast.ASTNode
import kotlin.reflect.KClass

typealias Span = @Composable RichTextScope.() -> Unit


/**
 * @since 3.0.0
 */
fun interface SpanFactory {
    fun getSpans(
        configuration: MarkwonConfiguration,
        props: AttributesContainer
    ): List<Span>
}

/**
 * Class that controls what spans are used for certain Nodes.
 *
 * @see io.noties.markwon.SpanFactory
 *
 * @since 3.0.0
 */
interface MarkwonSpansFactory {
    /**
     * Returns registered [io.noties.markwon.base.SpanFactory] or `null` if a factory for this node type
     * is not registered. There is [.require] method that will throw an exception
     * if required [io.noties.markwon.base.SpanFactory] is not registered, thus making return type `non-null`
     *
     * @param node type of the node
     * @return registered [io.noties.markwon.base.SpanFactory] or null if it\'s not registered
     * @see .require
     */
    operator fun <N : ASTNode> get(node: KClass<N>): SpanFactory?

    fun <N : ASTNode> require(node: KClass<N>): SpanFactory

    interface Builder {

        fun <N : ASTNode> setFactory(node: KClass<N>, factory: SpanFactory?): Builder

        fun <N : ASTNode> setFactory(node: KClass<N>, factory: (configuration: MarkwonConfiguration, props: AttributesContainer)->List<Span>): Builder

        /**
         * Helper method to add a [io.noties.markwon.SpanFactory] for a Node. This method will merge existing
         * [io.noties.markwon.SpanFactory] with the specified one.
         *
         * @since 3.0.1
         */
        @Deprecated(
            """4.2.2 consider using {@link #appendFactory(Class, io.noties.markwon.ext.SpanFactory)} or
          {@link #prependFactory(Class, io.noties.markwon.ext.SpanFactory)} methods for more explicit factory ordering.
          `addFactory` behaved like {@link #prependFactory(Class, io.noties.markwon.SpanFactory)}, so
          this method call can be replaced with it"""
        )
        fun <N : ASTNode> addFactory(node: KClass<N>, factory: SpanFactory): Builder

        /**
         * Append a factory to existing one (or make the first one for specified node). Specified factory
         * will be called **after** original (if present) factory. Can be used to
         * *change* behavior or original span factory.
         *
         * @since 4.2.2
         */
        fun <N : ASTNode> appendFactory(node: KClass<N>, factory: SpanFactory): Builder

        /**
         * Prepend a factory to existing one (or make the first one for specified node). Specified factory
         * will be called <string>before</string> original (if present) factory.
         *
         * @since 4.2.2
         */
        fun <N : ASTNode> prependFactory(node: KClass<N>, factory: SpanFactory): Builder

        /**
         * Can be useful when *enhancing* an already defined SpanFactory with another one.
         */
        fun <N : ASTNode> getFactory(node: KClass<N>): SpanFactory?

        /**
         * To obtain current [io.noties.markwon.SpanFactory] associated with specified node. Can be used
         * when SpanFactory must be present for node. If it\'s not added/registered a runtime
         * exception will be thrown
         *
         * @see .getFactory
         * @since 3.0.1
         */
        fun <N : ASTNode> requireFactory(node: KClass<N>): SpanFactory

        fun build(): MarkwonSpansFactory
    }

    companion object{
        fun createBuilder() = MarkwonSpansFactoryImpl.BuilderImpl()
    }

}

/**
 * @since 3.0.0
 */
class MarkwonSpansFactoryImpl(private val factories: Map<KClass<out ASTNode>, SpanFactory>) :
    MarkwonSpansFactory {
    override fun <N : ASTNode> get(node: KClass<N>): SpanFactory? {
        return factories[node]
    }

    override fun <N : ASTNode> require(node: KClass<N>): SpanFactory {
        return get(node) ?: throw NullPointerException(node.simpleName)
    }

    class BuilderImpl : MarkwonSpansFactory.Builder {
        private val factories: MutableMap<KClass<out ASTNode>, SpanFactory> = HashMap(3)
        override fun <N : ASTNode> setFactory(
            node: KClass<N>,
            factory: SpanFactory?
        ): MarkwonSpansFactory.Builder {
            if (factory == null) {
                factories.remove(node)
            } else {
                factories[node] = factory
            }
            return this
        }

        override fun <N : ASTNode> setFactory(
            node: KClass<N>,
            factory: (configuration: MarkwonConfiguration, props: AttributesContainer) -> List<Span>
        ): MarkwonSpansFactory.Builder {
            setFactory(node, SpanFactory { configuration, props -> factory(configuration, props) })
            return this
        }


        @Deprecated("")
        override fun <N : ASTNode> addFactory(
            node: KClass<N>,
            factory: SpanFactory
        ): MarkwonSpansFactory.Builder {
            return prependFactory(node, factory)
        }

        override fun <N : ASTNode> appendFactory(
            node: KClass<N>,
            factory: SpanFactory
        ): MarkwonSpansFactory.Builder {
            val existing = factories[node]
            if (existing == null) {
                factories[node] = factory
            } else {
                if (existing is CompositeSpanFactory) {
                    existing.factories.add(0, factory)
                } else {
                    val compositeSpanFactory = CompositeSpanFactory(factory, existing)
                    factories[node] = compositeSpanFactory
                }
            }
            return this
        }

        override fun <N : ASTNode> prependFactory(
            node: KClass<N>,
            factory: SpanFactory
        ): MarkwonSpansFactory.Builder {
            // if there is no factory registered for this node -> just add it
            val existing = factories[node]
            if (existing == null) {
                factories[node] = factory
            } else {
                // existing span factory can be of CompositeSpanFactory at this point -> append to it
                if (existing is CompositeSpanFactory) {
                    existing.factories.add(factory)
                } else {
                    // if it's not composite at this point -> make it
                    val compositeSpanFactory = CompositeSpanFactory(existing, factory)
                    factories[node] = compositeSpanFactory
                }
            }
            return this
        }

        override fun <N : ASTNode> getFactory(node: KClass<N>): SpanFactory? {
            return factories[node]
        }

        override fun <N : ASTNode> requireFactory(node: KClass<N>): SpanFactory {
            return getFactory(node) ?: throw NullPointerException(node.simpleName)
        }

        override fun build(): MarkwonSpansFactory {
            return MarkwonSpansFactoryImpl(factories)
        }
    }

    internal class CompositeSpanFactory(first: SpanFactory, second: SpanFactory) : SpanFactory {
        val factories: MutableList<SpanFactory> = ArrayList(3)

        init {
            factories.add(first)
            factories.add(second)
        }

        override fun getSpans(configuration: MarkwonConfiguration, props: AttributesContainer): List<Span> {
            // please note that we do not check it factory itself returns an array of spans,
            // as this behaviour is supported now (previously we supported only a single-level array)
            val length = factories.size
            val out = mutableListOf<Span>()
            for (i in 0 until length) {
                out.addAll(factories[i].getSpans(configuration, props))
            }
            return out
        }
    }
}